ASP.NET Core 2.0入门

MVC 增删改简单示例

作者:陈广 日期:2018-8-6


之前讲解了 Razor 的增删改简单示例,现在还差 MVC 没讲。那么这篇文章就讲 MVC 示例吧,效果还是按照之前的Razor示例来,只是把实现方法由 MVC 改成 Razor。

传统的 ASP.NET 应用程序使用的是 MVC 方式,也就是将应用程序分为 Model、View 和 Controller 层。而在 Razor 应用程序中,Controller 层和 Model 层是放在一起的,这样就简化了应用程序的复杂度,对于小型应用来说更为合适。

MVC 层中的 View 层是展示层,也就是在浏览器中显示的内容,我们可以把它想象成客户端的内容(实际上是浏览器向服务器发送请求,服务器返回页面 HTML,浏览器解析 HTML 并显示相应的内容)。客户端页面上有不同的按钮,点不同的按钮会向服务器申请不同的操作,而 Controller 层则是一个中转站,对不同的请求做相应处理,并导向不同的页面。可以把它想象成一个快递投递点,邮件从各地源源不断地进入投递点,然后再由投递点根据投递地址分配到不同地方。Controller 在处理请求的过程中可能会使用到数据库里的数据,它不会直接跟数据库打交道,而是通过一个代理人,Model就是这个代理人。这好比你开超市要卖全国各地的货,你不可能到全国各地直接找每个厂家要货,这太麻烦,成本太高。这时你就会找厂家在当地的经销商进货,和厂家打交道的麻烦事由经销商完成,这使得你可以专注于超市经营。

其实 Model 层并不直接操作数据库,Model 层里存在的是 C# 类,而这些类又跟数据库里的表一一对应。也就是说 Model 层将数据库架构映射为 C# 类。而 Model 层和数据库之间是通过 EF Core 进行连接的,实际上操作数据库的是 EF Core。另外,以上对 Model 的描述并非完全准确,如果数据存在复杂业务逻辑,也应该是放在 Model 层,Controller 只负责转发。这一点我们在将来写复杂的应用程序时再演示。

新建项目

本项目使用 .NET Core 2.1 以上版本,这样在使用数据库时无需引入额外的 nuget 包。

  1. 新建文件夹MvcDemo,在文件夹上点鼠标右键,选择Open with Code,使用Visual Studio Code打开这个文件夹。
  2. 按Ctrl+Shift+m打开终端,输入如下命令:
dotnet new empty

创建完项目后,可以看到自动生成的【MvcDemo.csproj】文件,打开它,确保<TargetFramework>标记内的 .NET Core 版本是 2.1 以上。

使用 SQLite 数据库

上次我们写 Razor 时,是使用 json 来代替数据库来存放数据。之前由于我们初步学习了 EF Core,现在就可以使用数据库了,如果使用的是 Visual Studio,在安装vs时,默认会安装 SQL Server,可以直接使用 SQL LocalDB,非常方便。但如果没有安装vs,就会有些麻烦。我现在是在外地使用笔记本写的这篇文章,不想装vs,也不想装 SQL Server 或 PostgreSQL,虽然可以远程访问我服务器上装的 PostgreSQL,但文章就不具有实用价值了,毕竟不是每个人都买有服务器。如何解决?这让我陷入了深深地深思。当然,这不难,有没有一个数据库体积小、安装方便、应用广泛,最重要的是 EF Core 还支持它?很幸运,SQLite 就是这么一款数据库。SQLite 号称嵌入式数据库,也就是在嵌入式系统里使用的数据库,体积自然会非常小,所有文件加起来不到 5M。拷贝即用,无需安装,在 Android 操作系统里有广泛应用。我们在vs code里使用都不需要安装,直接引入一个 nuget 包即可。

安装 Microsoft.EntityFrameworkCore.Sqlite

在vs code中按下【Shift+Ctrl+Y】键打开终端,输入如下命令:

dotnet add package Microsoft.EntityFrameworkCore.Sqlite

安装完成后会弹出一个对话框,点击 Restore 按钮即可引用此包。如下图所示: 如果没有出现 Restore 对话框也可以输入如下命令达到同样效果:

dotnet restore

创建数据库实体类

在《Razor增删改简单示例》这篇文章中,我们可以了解到数据库非常简单,只有一张 Studentd 表,下面我们针对这张表创建实体类。在 MvcDemo 下创建一个名为 Models 的文件夹。在 Models 文件夹下新建一个 Student.cs 文件,输入如下代码:

namespace MvcDemo.Models
{
    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

Models 文件夹下新建一个 DataContext.cs 文件,输入如下代码:

using Microsoft.EntityFrameworkCore;

namespace MvcDemo.Models
{
    public class DataContext : DbContext
    {
        public DataContext(DbContextOptions<DataContext> options) : base(options) { }
        public DbSet<Student> Students { get; set; }
    }
}

添加依赖

在 MvcDemo 下新建一个 appsettings.json 文件,我们将在这里存放数据库连接字符串:

{
    "ConnectionStrings": {
        "DefaultConnection": "Data Source=Stuinfo.db"
    }
}

如果你需要使用 SQL LocalDB,内需更改 DefaultConnection 项里的连接字符串即可,在.NET Core 2.1 下无需引入任何 nuget 包。

打开 Startup.cs 文件,增加如下命名空间:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using MvcDemo.Models;

更改Startup类代码如下:

public Startup(IConfiguration config) => Configuration = config;
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    string conStr = Configuration["ConnectionStrings:DefaultConnection"];
    services.AddDbContext<DataContext>(options => options.UseSqlite(conStr));
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseDeveloperExceptionPage();
    app.UseStatusCodePages();
    app.UseStaticFiles();
    app.UseMvcWithDefaultRoute();
}

数据库迁移

下面我们使用命令的方式生成相应的数据库。在终端输入如下两条命令:

dotnet ef migrations add Initial
dotnet ef database update

两条命令执行完毕后,我们在资源管理器串可以看到相应的 SQLite 数据库文件已经生成,如下图:

查看 SQLite 数据库

现在问题来了,Stuinfo.db 是一个二进制文件,vs code是无法打开它的,打开了也是乱码。我又如何知道学生表是否在此数据库中已经被创建了呢?不用担心,万能的 vs code 可以轻易解决这个问题。

在vs code中安装 SQLite 插件

点击vs code 左侧的 扩展 按钮(也可使用【Ctrl+Shift+X】热键调出,但有可能快捷键已被其它插件占用)打开扩展窗口,在搜索栏输入 SQLite,可搜索到 SQLite插件,如下图所示: 点击插件右下方的安装按钮以安装插件,安装完毕后点击重新加载按钮: 现在,我们可以查看 SQLite 数据库了。

查看数据表

按下【Ctrl+Shift+P】打开命令面板(也可通过菜单【查看】-->【命令面板】打开)。输入SQLite命令并选择其中的 Open Database in Explorer 命令: 接下来观察vs code的资源管理最下方,出现了【SQLITE EXPLORER】,展开它,可观察到【Stuinfo.db】数据库,继续展开,出现【Students】表及它的字段。如下图所示,我们已经成功在 SQLite 中创建了相应的表。

添加数据

接下来往 Students 表中添加两条数据数据以方便我们查看页面效果。

在命令面板中选择【SQLite:Quick Query】命令 : 依次输入两条 SQL 查询语句:

insert into Students values(1,"ZhangSan")
insert into Students values(2,"LiSi")

最后,输入以下语句查看插入效果:

select * from Students

在弹出的 SQLite 窗口中可以查看到如下图所示的表中数据,表明成功插入两条数据。 请注意,不要插入中文,否则显示乱码。

创建公共页面

如果使用 MVC 模板创建项目,会自动包含公共页面,现在我们是白手起家的操作方式,所以这些东西还得自力更生。

_Layout.cshtml

在 MvcDemo 项目下新建一个 Views 文件夹,然后在 Views 文件夹内新建一个 Home 文件夹。接下来在 Home 文件夹下新建一个 _Layout.cshtml 文件,输入如下代码:

<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>学生信息</title>
    </head>
    <body>
        @RenderBody()   
    </body>
</html>

此页面的内容会在所有页面显示。

_ViewStart.cshtml

Views 文件夹下新建一个名为 _ViewStart.cshtml 的文件,输入如下代码:

@{
    Layout = "_Layout";
}

此文件指定了公共页,"_Layout"正是刚刚创建的公共页的文件名。

_ViewImports.cshtml

Views 文件夹下新建一个名为 _ViewImports.cshtml 的文件,输入如下代码:

@using MvcDemo.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

此处引入的内容会作用于 Views 文件夹下的所有页面,所以所有页面都需要引用的东西都应该放在这里。

创建主页面

下面我们来创建主页面,主页面主是用来显示所有学生的信息。

主页面控制器

Razor中,控制器是和页面放在一起的,但 MVC 中,页面和控制器是分开的,所以需要单独创建控制器。

在 MvcDemo 项目下新建一个 Controllers 文件夹,然后在 Controllers 文件夹下新建一个 HomeController.cs 文件,输入如下代码:

using Microsoft.AspNetCore.Mvc;
using MvcDemo.Models;

namespace MvcDemo.Controllers
{
    public class HomeController : Controller
    {
        private DataContext context;
        public HomeController(DataContext ctx) => context = ctx;
        public IActionResult Index() => View(context.Students);
    }
}

这里创建的控制器的文件名是有讲究的,Home前缀正好对应 Views 文件夹下的 Home 文件夹名称,表明此控制器操作的是 Home 文件夹下的页面。在访问页面时,如果不在 URL 中指定任何后缀,则默认调用控制器中的Index()方法。Index()方法将所有学生信息context.Students作为View的参数返回给页面进行处理。

主页面

下面创建主页面。在 Home 文件夹下创建一个 Index.cshtml 文件,输入如下代码:

@model IEnumerable<Student>
<h1>学生列表</h1>

<table class="table">
    <thead>
        <tr>
            <th>学号</th>
            <th>姓名</th>
        </tr>
    </thead>
    <tbody>
        @foreach(var stu in Model)
        {
            <tr>
                <td>@stu.Id</td>
                <td>@stu.Name</td>
            </tr>
        }
    </tbody>
</table>

这里需要注意 MVC 和 Razor 的不同之处,以及第一句@model IEnumerable<Student> 和之前控制器Index()方法中的View(context.Students)之间的关系。

  1. 浏览器发出页面请求,请求 Index 页面。
  2. 请求传递到服务器后,服务器将请求交由 Controller 处理。
  3. Controller 解析请求,并交由 Index() 方法处理。
  4. Index() 方法返回 View(context.Students),表示返回 Index.cshtml 页面并将context.Students作为参数传递过去。
  5. Index.cshtml中接收参数context.Students的就是@model IEnumerable<Student>,它表示参数必须是一个实现了IEnumerable接口的Student类集合。在 Index.cshtml 中可通过Model来使用此参数。
  6. 服务器处理 Index.cshtml 页面,根据参数生成相应 HTML 然后返回给客户端浏览器。

现在可以运行程序查看效果了。在终端输入如下命令来运行程序:

dotnet run

效果如下图所示: 按住 Ctrl 键的同时用鼠标点击如上图所示的http://localhost:5000即可打开浏览器并导航到主页面。当然你也可以打开浏览器,然后在地址栏输入http://localhost:5000来访问主页面。

程序运行效果如下图所示: 现在已经可以显示之前我们输入的两条数据了。

添加新项页面

下面参照之前的《Razor增删改简单示例》这篇文章,创建 Create 页面,用于向数据库中添加学生数据。

修改控制器

打开 HomeController.cs 文件,并更改代码如下:

using Microsoft.AspNetCore.Mvc;
using MvcDemo.Models;

namespace MvcDemo.Controllers
{
    public class HomeController : Controller
    {
        private DataContext context;
        public HomeController(DataContext ctx) => context = ctx;
        public IActionResult Index() => View(context.Students);
        //以下为新增代码
        public IActionResult Create() => View();
        [HttpPost]
        public IActionResult Create(Student stu)
        {
            context.Students.Add(stu);
            context.SaveChanges();
            return RedirectToAction(nameof(Index)); //重定向到主页面
        }
    }
}

我们在控制器中新增了两个Create方法:

  • 第一个Create方法直接返回 Create.cshtml页面,此页面稍后会创建。当我们在浏览器中输入http://localhost:5000/home/create 地址时,controller会调用此方法以显示新建学生页面。
  • 第二个Create方法被标识为[HttpPost]特性,表明只有在method="POST"的表单中点击了提交按钮,才会被导航到此方法。

Create页面

下面来制作新建学生页面。在 View/Home 文件夹下新建一个文件,命名为 Create.cshtml。输入如下代码:

@model Student

<html>
    <body>
        <p>请输入新的学生信息</p>
        <form asp-action="Create" method="POST">
            <div>学号:<input asp-for="Id"/></div>
            <div>姓名:<input asp-for="Name"/></div>
            <input type="submit" value="提交"/>
        </form>
    </body>
</html>

表单form中的标签助手asp-action="Create"表示此表单提交后会交由 Controller 中的Create方法进行处理。method="POST"对应的正是 Controller 中的[HttpPost]特性。

修改 Index.cshtml 页面

接下来需要在主页面添加一个导航到 Create.cshtml 页面的链接,打开 Index.cshtml 文件,并在最后添加一句代码:

<a asp-action="Create">新建</a>

接下来运行程序,并添加几条数据。如果过程有不清楚的可以参考《Razor增删改简单示例》这篇文章的配图。我就不再做一遍了。

删除功能

删除功能比较简单,不需要添加新的文件。

修改控制器

打开 HomeController.cs 文件,在HomeController类中添加一个新的方法:

[HttpPost]
public IActionResult Delete(Student stu){
    context.Students.Remove(stu);
    context.SaveChanges();
    return RedirectToAction(nameof(Index));
}

使用[HttpPost]表明在页面文件中需要使用表单来完成删除功能。

修改主页面

打开 View/Home/Index.cshtml 文件,修改代码如下:

@model IEnumerable<Student>
<h1>学生列表</h1>

<table class="table">
    <thead>
        <tr>
            <th>学号</th>
            <th>姓名</th>
        </tr>
    </thead>
    <tbody>
        @foreach(var stu in Model)
        {
            
                <tr>
                    <td>@stu.Id</td>
                    <td>@stu.Name</td>
                    <!-- 以下为新添加的代码 -->
                    <form asp-action="Delete" method="POST">
                        <input type="hidden" name="Id" value="@stu.Id" />
                        <td><button type="submit">删除</button></td>
                    </form> 
                </tr>
            
        }
    </tbody>
</table>
<a asp-action="Create">新建</a>

需要注意,为了使用《Razor增删改简单示例》这篇文章中的效果而使用按钮进行删除,这里使用了表单,所以必须要增加了一个<input>标签来存放学生的Id字段。在 MVC 中暂时没找到象 Razor 那样使用asp-page-handler标签助手就能方便解决问题的方法。你也可以使用<a>标签来很方便实现删除功能,参考之前的【新建】链接。

编辑功能

修改控制器

打开 HomeController.cs 文件,在HomeController类中添加2个新的方法:

public IActionResult Edit(int key)
{   
    Student stu = context.Students.Find(key);
    return View(stu);
}
[HttpPost]
public IActionResult Update(Student stu)
{
    context.Students.Update(stu);
    context.SaveChanges();
    return RedirectToAction(nameof(Index));
}

和 Create 一样,需要写两个方法。

  • 第一个方法Edit(int key)用于将链接导向名为Edit.cshtml的页面,链接中需要给出学生的 Id 以查找出相应的学生并传递给 Edit 页面进行修改。return View(stu);用于返回 Edit 页面并将学生对象传递过去。
  • 第二个方法Update(Student stu)则用于在 Edit 页面修改完成后的保存工作,最终它会导向主页面显示修改后的效果。

创建编辑页面

Views/Home 文件夹下新建一个名为 Edit.cshtml 的文件,输入代码如下:

@model Student

<h1>编辑学生-Id</h1>
<form asp-action="Update" method="POST">
    <input asp-for="Id" type="hidden"/>
    <div>
        <input asp-for="Name"/>
    </div>
    <div>
        <button type="submit">保存</button>
    </div>
</form>

注意,文件名 Edit 对应控制器中的Edit方法。asp-action="Update"表明单击底下的按钮会导航到控制器中的Update方法。

修改主页面

打开 View/Home/Index.cshtml 文件,修改其中的<tbody>代码如下:

<tbody>
    @foreach(var stu in Model)
    {
        
        <tr>
            <td>@stu.Id</td>
            <td>@stu.Name</td>
            <!-- 下面这句为新添加的代码 -->
            <td><a asp-action="Edit" asp-route-key="@stu.Id">编辑</a></td>
            <form asp-action="Delete" method="POST">
                <input type="hidden" name="Id" value="@stu.Id" />
                <td><button type="submit">删除</button></td>
            </form> 
        </tr>        
    }
</tbody>

asp-action="Edit"指定调用控制器中的Edit方法,asp-route-key则传递了学生Id。

现在可以运行程序,并编辑数据了。效果和《Razor增删改简单示例》这篇文章中的一样,我就不再配图了。

之所以把这个例子跟之前的 Razor 做得一模一样,主要是让大家感受到 Razor 和 MVC 的某些细节上的差异。我个人感觉最大的不同是 MVC 对数据库中的同一个表,不管有多少页面,处理方法都可以放在同一个控制器内,而 Razor 则是每个页面都有自己的控制器。Razor 页面可以很方便找到自己的控制器代码,MVC 则要费一些周折。孰优熟劣大家自己感受,又或说它们都有自己适合的应用场景。

;

© 2018 - IOT小分队文章发布系统 v0.3